# Web界面后端服务 - 连接多Agent系统的Web API
from fastapi import FastAPI, HTTPException, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from typing import List, Dict, Any
import asyncio
import json
import uvicorn
from datetime import datetime
import requests
import logging

# 配置日志
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

app = FastAPI(
    title="A2A多智能体Web界面",
    description="A2A多智能体协作系统的Web前端API",
    version="1.0.0"
)

# 允许跨域请求
app.add_middleware(
    CORSMiddleware,
    allow_origins=["*"],  # 生产环境应该限制具体域名
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

# 数据模型
class ChatMessage(BaseModel):
    message: str
    user_id: str = "default_user"

class ChatResponse(BaseModel):
    response: str
    timestamp: str
    agents_used: List[str] = []
    processing_time: float

class NegotiationEvent(BaseModel):
    """前端协商过程实时事件模型"""
    type: str  # 事件类型: plan_created/step_start/step_result/finalizing/final_result/progress
    session_id: str | None = None
    step: Dict[str, Any] | None = None  # 可选的步骤数据
    data: Dict[str, Any] | None = None  # 兼容字段

# 连接管理器
class ConnectionManager:
    def __init__(self):
        self.active_connections: List[WebSocket] = []

    async def connect(self, websocket: WebSocket):
        await websocket.accept()
        self.active_connections.append(websocket)
        logger.info(f"WebSocket连接已建立，当前连接数: {len(self.active_connections)}")

    def disconnect(self, websocket: WebSocket):
        self.active_connections.remove(websocket)
        logger.info(f"WebSocket连接已断开，当前连接数: {len(self.active_connections)}")

    async def send_personal_message(self, message: str, websocket: WebSocket):
        await websocket.send_text(message)

    async def broadcast(self, message: str):
        for connection in self.active_connections:
            await connection.send_text(message)

manager = ConnectionManager()

# Agent配置
AGENTS_CONFIG = {
    "coordinator": {
        "url": "http://localhost:7006",
        "name": "协调器智能体",
        "description": "负责分析用户意图并协调其他智能体"
    },
    "weather": {
        "url": "http://localhost:7003", 
        "name": "天气智能体",
        "description": "提供实时天气信息和活动建议"
    },
    "travel": {
        "url": "http://localhost:7004",
        "name": "旅游智能体", 
        "description": "推荐景点和旅游计划"
    },
    "restaurant": {
        "url": "http://localhost:7005",
        "name": "餐厅智能体",
        "description": "推荐美食和餐厅"
    },
    "budget": {
        "url": "http://localhost:7007",
        "name": "预算智能体",
        "description": "智能预算分析和成本优化"
    }
}

# API端点
@app.get("/api/agents/status")
async def get_agents_status():
    """获取所有Agent的状态"""
    status = {}
    
    for agent_id, config in AGENTS_CONFIG.items():
        try:
            response = requests.get(config["url"], timeout=5)
            status[agent_id] = {
                "name": config["name"],
                "description": config["description"],
                "status": "在线" if response.status_code == 200 else "离线",
                "url": config["url"]
            }
        except Exception as e:
            status[agent_id] = {
                "name": config["name"],
                "description": config["description"],
                "status": "离线",
                "error": str(e),
                "url": config["url"]
            }
    
    return status

@app.post("/api/chat", response_model=ChatResponse)
async def chat_with_agents(message: ChatMessage):
    """与多智能体系统聊天"""
    start_time = datetime.now()
    
    try:
        # 调用协调器Agent
        coordinator_url = AGENTS_CONFIG["coordinator"]["url"]
        
        # 构建请求
        payload = {
            "message": {
                "content": {
                    "text": message.message
                }
            }
        }
        
        logger.info(f"发送请求到协调器: {message.message}")
        
        # 发送请求到协调器
        response = requests.post(
            f"{coordinator_url}/a2a/tasks/send",
            json=payload,
            timeout=300  # 5分钟超时 - 复杂多Agent协作需要更长时间
        )
        
        if response.status_code == 200:
            result = response.json()
            
            # 提取响应文本
            if "artifacts" in result and len(result["artifacts"]) > 0:
                response_text = result["artifacts"][0]["parts"][0]["text"]
            else:
                response_text = "系统处理完成，但没有返回具体内容。"
            
            # 计算处理时间
            processing_time = (datetime.now() - start_time).total_seconds()
            
            return ChatResponse(
                response=response_text,
                timestamp=datetime.now().isoformat(),
                agents_used=result.get("agents_used", ["coordinator"]),
                processing_time=processing_time
            )
        else:
            raise HTTPException(
                status_code=500, 
                detail=f"协调器返回错误: {response.status_code} - {response.text}"
            )
            
    except requests.exceptions.Timeout:
        raise HTTPException(status_code=504, detail="请求超时，请稍后重试")
    except requests.exceptions.ConnectionError:
        raise HTTPException(status_code=503, detail="无法连接到协调器服务，请检查服务是否运行")
    except Exception as e:
        logger.error(f"聊天处理错误: {str(e)}")
        raise HTTPException(status_code=500, detail=f"系统内部错误: {str(e)}")

@app.websocket("/ws")
async def websocket_endpoint(websocket: WebSocket):
    """WebSocket端点，支持实时聊天和协商过程展示"""
    await manager.connect(websocket)
    try:
        while True:
            # 接收客户端消息
            data = await websocket.receive_text()
            message_data = json.loads(data)
            
            # 根据消息类型处理
            if message_data.get('type') == 'start_negotiation':
                # 开始协商过程
                await handle_negotiation_process(websocket, message_data)
            else:
                # 普通聊天消息
                await handle_chat_message(websocket, message_data)
                
    except WebSocketDisconnect:
        manager.disconnect(websocket)

@app.post("/api/negotiation/event")
async def negotiation_event(event: NegotiationEvent):
    """接收协调器推送的协商事件并通过WebSocket广播到前端"""
    try:
        payload = {
            "type": event.type,
            # 统一使用 step 字段，兼容前端已有解析逻辑
            "step": event.step or event.data or {}
        }
        await manager.broadcast(json.dumps(payload, ensure_ascii=False))
        return {"status": "ok"}
    except Exception as e:
        logger.error(f"广播协商事件失败: {e}")
        raise HTTPException(status_code=500, detail=str(e))

async def handle_chat_message(websocket: WebSocket, message_data: dict):
    """处理普通聊天消息"""
    # 发送状态更新
    await manager.send_personal_message(
        json.dumps({"type": "status", "message": "正在处理您的请求..."}),
        websocket
    )
    
    try:
        # 调用聊天API
        chat_message = ChatMessage(
            message=message_data["message"],
            user_id=message_data.get("user_id", "default_user")
        )
        
        response = await chat_with_agents(chat_message)
        
        # 发送响应
        await manager.send_personal_message(
            json.dumps({
                "type": "response",
                "data": response.dict()
            }),
            websocket
        )
        
    except Exception as e:
        await manager.send_personal_message(
            json.dumps({
                "type": "error",
                "message": f"处理请求时出现错误: {str(e)}"
            }),
            websocket
        )

async def handle_negotiation_process(websocket: WebSocket, message_data: dict):
    """处理协商过程，实时展示Agent间的思考和决策"""
    
    user_message = message_data["message"]
    details = message_data.get("details", {})
    
    # 获取用户输入的目的地，如果没有则使用默认值
    destination = details.get('destination', '欧洲').strip()
    if not destination:
        destination = '欧洲'
    
    logger.info(f"开始真实Agent协商过程: {user_message}, 目的地: {destination}")
    
    # 步骤1: Coordinator 分析任务
    await send_negotiation_step(websocket, {
        "type": "thinking",
        "agent": "CoordinatorAgent",
        "title": "📋 CoordinatorAgent: 任务分析",
        "content": f"正在分析旅行需求，准备分配给专业Agent... 目的地: {destination}"
    })
    
    await asyncio.sleep(1)
    
    # 步骤2: 发送协作请求给WeatherAgent
    await send_negotiation_step(websocket, {
        "type": "collaboration",
        "from_agent": "CoordinatorAgent",
        "to_agent": "WeatherAgent", 
        "title": "🌤️ 发送协作请求: 天气分析",
        "content": f"请求WeatherAgent分析{destination}天气状况和影响"
    })
    
    await asyncio.sleep(1)
    
    # 调用真实的WeatherAgent
    weather_analysis = await get_real_weather_collaboration(destination)
    
    await send_negotiation_step(websocket, {
        "type": "response",
        "agent": "WeatherAgent",
        "title": "🌤️ WeatherAgent 分析结果",
        "content": weather_analysis["analysis"],
        "negotiation_points": weather_analysis.get("negotiation_points", [])
    })
    
    # 步骤3: 检测到协商点，触发BudgetAgent协作
    if weather_analysis.get("negotiation_points"):
        await send_negotiation_step(websocket, {
            "type": "trigger",
            "title": "🔍 检测到协商点",
            "content": f"WeatherAgent发现{len(weather_analysis['negotiation_points'])}个需要预算考虑的问题"
        })
        
        await asyncio.sleep(0.5)
        
        # 发送协作请求给BudgetAgent
        await send_negotiation_step(websocket, {
            "type": "collaboration",
            "from_agent": "CoordinatorAgent",
            "to_agent": "BudgetAgent",
            "title": "💰 发送协作请求: 预算优化",
            "content": f"请求BudgetAgent基于天气分析进行预算优化"
        })
        
        await asyncio.sleep(1)
        
        # 调用真实的BudgetAgent
        budget_analysis = await get_real_budget_collaboration(weather_analysis, details)
        
        await send_negotiation_step(websocket, {
            "type": "response",
            "agent": "BudgetAgent",
            "title": "💰 BudgetAgent 预算分析",
            "content": budget_analysis["analysis"],
            "conflicts": budget_analysis.get("conflicts", []),
            "proposals": budget_analysis.get("proposals", [])
        })
        
        # 步骤4: 展示冲突和协商
        if budget_analysis.get("conflicts"):
            await send_negotiation_step(websocket, {
                "type": "conflict",
                "title": "⚠️ 发现Agent间冲突",
                "content": f"WeatherAgent和BudgetAgent在{len(budget_analysis['conflicts'])}个方面存在分歧",
                "conflicts": budget_analysis["conflicts"]
            })
            
            await asyncio.sleep(1)
            
            # 协商过程
            await send_negotiation_step(websocket, {
                "type": "negotiation",
                "title": "🤝 Agent间协商进行中",
                "content": "BudgetAgent正在与WeatherAgent协商最优方案...",
                "negotiation_flow": [
                    "BudgetAgent: 建议调整部分日期以平衡成本",
                    "WeatherAgent: 评估替代日期的天气风险",
                    "双方Agent: 寻找成本效益最优解"
                ]
            })
            
            await asyncio.sleep(2)
        
        # 步骤5: 协商结果
        await send_negotiation_step(websocket, {
            "type": "agreement",
            "title": "✅ Agent协商达成一致",
            "content": f"基于{destination}的天气和预算分析，WeatherAgent和BudgetAgent达成平衡方案",
            "final_proposal": budget_analysis.get("final_proposal", "混合优化方案")
        })
    
    logger.info("真实Agent协商过程完成")

async def send_negotiation_step(websocket: WebSocket, step_data: dict):
    """发送协商步骤到客户端"""
    message = {
        "type": step_data.get("type", "negotiation_step"),
        "step": step_data
    }
    
    await manager.send_personal_message(json.dumps(message), websocket)

async def get_weather_analysis(destination: str) -> str:
    """获取天气分析信息"""
    try:
        # 调用真实的WeatherAgent
        weather_agent_url = AGENTS_CONFIG["weather"]["url"]
        
        payload = {
            "message": {
                "content": {
                    "text": json.dumps({
                        "type": "collaboration_request",
                        "user_requirements": {"location": destination},
                        "collaboration_context": {"purpose": "travel_planning"}
                    }, ensure_ascii=False)
                }
            }
        }
        
        response = requests.post(
            f"{weather_agent_url}/a2a/tasks/send",
            json=payload,
            timeout=10
        )
        
        if response.status_code == 200:
            result = response.json()
            if "artifacts" in result and len(result["artifacts"]) > 0:
                weather_response = json.loads(result["artifacts"][0]["parts"][0]["text"])
                
                # 提取关键信息用于前端展示
                weather_data = weather_response.get("weather_data", {})
                impact_analysis = weather_response.get("impact_analysis", {})
                
                # 构建更详细的天气分析
                analysis_parts = []
                analysis_parts.append(f"{destination}天气: {weather_data.get('current', '数据获取中')}")
                
                if weather_data.get("forecast_3day"):
                    forecast = weather_data["forecast_3day"]
                    analysis_parts.append(f"3日预报: {len(forecast)}天详细预报已分析")
                
                if impact_analysis.get("outdoor_suitability"):
                    score = impact_analysis["outdoor_suitability"]
                    suitability = "很适宜" if score >= 8 else "适宜" if score >= 6 else "一般" if score >= 4 else "不太适宜"
                    analysis_parts.append(f"户外活动适宜度: {suitability}({score}/10)")
                
                negotiation_points = weather_response.get("negotiation_points", [])
                if negotiation_points:
                    analysis_parts.append(f"检测到{len(negotiation_points)}个需要协商的优化点")
                
                return " | ".join(analysis_parts)
        
    except Exception as e:
        logger.warning(f"调用WeatherAgent失败: {e}")
    
    # 返回模拟数据
    return f"{destination}天气预报：明天多云，15°C；后天雨天，12°C；大后天晴天，18°C。雨天对户外摄影影响较大，建议调整行程或准备室内替代方案。"

async def get_real_weather_collaboration(destination: str) -> dict:
    """获取真实的WeatherAgent协作分析结果"""
    try:
        weather_agent_url = AGENTS_CONFIG["weather"]["url"]
        
        payload = {
            "message": {
                "content": {
                    "text": json.dumps({
                        "type": "collaboration_request",
                        "user_requirements": {"location": destination},
                        "collaboration_context": {"purpose": "travel_planning"}
                    }, ensure_ascii=False)
                }
            }
        }
        
        response = requests.post(
            f"{weather_agent_url}/a2a/tasks/send",
            json=payload,
            timeout=10
        )
        
        if response.status_code == 200:
            result = response.json()
            if "artifacts" in result and len(result["artifacts"]) > 0:
                weather_response = json.loads(result["artifacts"][0]["parts"][0]["text"])
                
                # 提取关键信息用于前端展示
                weather_data = weather_response.get("weather_data", {})
                impact_analysis = weather_response.get("impact_analysis", {})
                negotiation_points = weather_response.get("negotiation_points", [])
                
                # 构建分析结果
                analysis_text = f"{destination}天气分析完成："
                if weather_data.get("current"):
                    analysis_text += f" 当前{weather_data['current']}"
                if impact_analysis.get("outdoor_suitability"):
                    score = impact_analysis["outdoor_suitability"]
                    analysis_text += f", 户外活动适宜度{score}/10"
                
                return {
                    "analysis": analysis_text,
                    "weather_data": weather_data,
                    "impact_analysis": impact_analysis,
                    "negotiation_points": negotiation_points
                }
        
    except Exception as e:
        logger.warning(f"调用WeatherAgent协作失败: {e}")
    
    # 返回模拟协作数据
    return {
        "analysis": f"{destination}天气分析：多云转雨，户外活动受限",
        "negotiation_points": [
            {"issue": "雨天影响户外活动", "cost_impact": 150, "urgency": "high"},
            {"issue": "需要室内替代方案", "cost_impact": 80, "urgency": "medium"}
        ]
    }

async def get_real_budget_collaboration(weather_analysis: dict, details: dict) -> dict:
    """获取真实的BudgetAgent协作分析结果"""
    try:
        budget_agent_url = AGENTS_CONFIG.get("budget", {}).get("url", "http://localhost:8004")
        
        payload = {
            "message": {
                "content": {
                    "text": json.dumps({
                        "type": "collaboration_request",
                        "weather_context": weather_analysis,
                        "user_requirements": {
                            "budget": details.get("budget", "800欧元"),
                            "destination": details.get("destination", "欧洲"),
                            "duration": details.get("duration", "3天")
                        },
                        "collaboration_context": {"purpose": "budget_optimization"}
                    }, ensure_ascii=False)
                }
            }
        }
        
        response = requests.post(
            f"{budget_agent_url}/a2a/tasks/send",
            json=payload,
            timeout=10
        )
        
        if response.status_code == 200:
            result = response.json()
            if "artifacts" in result and len(result["artifacts"]) > 0:
                budget_response = json.loads(result["artifacts"][0]["parts"][0]["text"])
                
                return {
                    "analysis": budget_response.get("budget_analysis", "预算分析完成"),
                    "conflicts": budget_response.get("potential_conflicts", []),
                    "proposals": budget_response.get("counter_proposals", []),
                    "final_proposal": budget_response.get("recommendation", "混合优化方案")
                }
        
    except Exception as e:
        logger.warning(f"调用BudgetAgent协作失败: {e}")
    
    # 返回模拟协作数据
    return {
        "analysis": "预算分析：天气因素将增加室内活动成本约120欧元",
        "conflicts": [
            "WeatherAgent建议室内活动 vs 预算限制冲突",
            "天气应对成本 vs 原计划预算分配冲突"
        ],
        "proposals": [
            "调整20%预算到天气应对",
            "选择成本效益更高的室内活动"
        ],
        "final_proposal": "混合方案：部分室内活动 + 灵活户外计划，预算控制在850欧元"
    }

@app.get("/health")
async def health_check():
    """健康检查端点"""
    return {
        "status": "healthy",
        "timestamp": datetime.now().isoformat(),
        "agents_count": len(AGENTS_CONFIG)
    }

# 挂载静态文件目录（放在最后，避免与API路由冲突）
import os
frontend_path = os.path.join(os.path.dirname(__file__), "../frontend")
app.mount("/", StaticFiles(directory=frontend_path, html=True), name="frontend")

if __name__ == "__main__":
    print("🚀 启动A2A多智能体Web界面后端服务...")
    print("📍 服务地址: http://localhost:8000")
    print("📖 API文档: http://localhost:8000/docs")
    print("🔗 WebSocket: ws://localhost:8000/ws")
    
    uvicorn.run(
        "web_backend:app",
        host="0.0.0.0",
        port=8000,
        reload=True,
        log_level="info"
    )
